//
//  LuaP.cpp
//  LuaP
//
//  Created by liyong on 2016/10/18.
//  Copyright © 2016年 liyong. All rights reserved.
//

#include "LuaP.hpp"
#include <chrono>
#include <cstdlib>
//#include "easylogging++.h"
#import "CocoaLumberjack.h"
using namespace std;

//INITIALIZE_EASYLOGGINGPP

static const DDLogLevel ddLogLevel = DDLogLevelAll;

extern "C" {
    
    static void callhook(lua_State *L, lua_Debug *ar) {
        lua_Debug previous_ar;
        lua_getstack(L, 1, &previous_ar);
        lua_getinfo(L, "n", ar);
        LuaP::getInstance().CallFunc(ar);
        
    }
    static void *lmp_free(void *ptr) {
        LuaP::getInstance().Free(ptr);
        free(ptr);
        return NULL;
    }
    static void *lmp_malloc(size_t nsize, size_t luatype) {
        void *ptr = malloc(nsize);
        LuaP::getInstance().Malloc(ptr, nsize, luatype);
        return ptr;
    }
    static void *lmp_realloc(void *ptr, size_t nsize) {
        void *p = realloc(ptr, nsize);
        LuaP::getInstance().Realloc(ptr, nsize, p);
        return p;
    }
    static void *lmp_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
        if(nsize == 0) {
            return lmp_free(ptr);
        }else if(ptr == NULL) {
            return lmp_malloc(nsize, osize);
        }else {
            return lmp_realloc(ptr, nsize);
        }
    }
}

void LuaP::Realloc(void *ptr, size_t nsize, void *p) {
    if(p == NULL) {
    }else {
        auto find = memoryBlocks.find((intptr_t)ptr);
        if(find != memoryBlocks.end()) {
            auto sec = find->second;
            memoryBlocks.erase(find);
            memoryBlocks[(intptr_t)p] = sec;
            auto oldSize = sec->size;
            sec->size = nsize;
            
            auto lt = sec->luatype;
            PrepareType(lt);
            totalMemory[lt]->size -= oldSize;
            totalMemory[lt]->size += nsize;
        
            
        }
        
    }
}

void LuaP::Malloc(void *ptr, size_t nsize, size_t luatype) {
    //LOG(INFO) << "TP:" << luatype << ":" << nsize;
    auto lmpBlock = shared_ptr<lmp_block>(new lmp_block());
    lmpBlock->size = nsize;
    lmpBlock->luatype = luatype;
    memoryBlocks[(intptr_t)ptr] = lmpBlock;
    
    PrepareType(luatype);
    totalMemory[luatype]->size += nsize;
    
    if((uintptr_t)ptr > maxAddress) {
        maxAddress = (uintptr_t)ptr;
    }
}

void LuaP::Free(void *ptr) {
    
    auto find = memoryBlocks.find((intptr_t)ptr);
    if(find != memoryBlocks.end()) {
        auto lt = find->second->luatype;
        
        auto sz = find->second->size;
        
        PrepareType(lt);
        totalMemory[lt]->size -= sz;
        freeSize[lt]->size += sz;
        
        memoryBlocks.erase(find);
        
    }
}

void LuaP::SetLogPath(string path) {
    logPath = path;
    [DDLog addLogger:[DDTTYLogger sharedInstance]];
    DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
    fileLogger.rollingFrequency = 60*60*24;
    fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
    //fileLogger.
    [DDLog addLogger:fileLogger];
    
    /*
    el::Configurations defaultConf;
    defaultConf.setToDefault();
    defaultConf.set(el::Level::Info, el::ConfigurationType::Enabled, "true");
    defaultConf.set(el::Level::Info, el::ConfigurationType::Format, "%datetime %msg");
    //defaultConf.set(el::Level::Info, el::ConfigurationType::ToFile, "true");
    defaultConf.set(el::Level::Info, el::ConfigurationType::Filename, logPath);
    //defaultConf.set(el::Level::Info, el::ConfigurationType::ToStandardOutput, "false");
    
    el::Loggers::reconfigureLogger("default", defaultConf);
    */
}

void LuaP::PrepareType(size_t luatype) {
    if(totalMemory.find(luatype) == totalMemory.end()) {
        auto lp = totalMemory[luatype] = shared_ptr<lmp_block>(new lmp_block());
        lp->luatype = luatype;
        lp->size = 0;
    }
    if(freeSize.find(luatype) == freeSize.end()) {
        auto lp = freeSize[luatype] = shared_ptr<lmp_block>(new lmp_block());
        lp->luatype = luatype;
        lp->size = 0;
    }
}
void LuaP::Start(lua_State *L){
    lowAddress = (uintptr_t)L;
    maxAddress = lowAddress;
    
    
    
    lua_sethook(L, (lua_Hook)callhook, LUA_MASKCALL | LUA_MASKRET, 0);
    lua_setallocf(L, lmp_alloc, NULL);
}

void LuaP::CallFunc(lua_Debug *ar) {
    if(ar->name == NULL) {
        return;
    }
    
    auto ret = functionCalls.find(ar->name);
    if(ret == functionCalls.end()){
        if(ar->event == 1) {
            return;
        }
        
        shared_ptr<FunctionCallInfo> fc(new FunctionCallInfo());
        functionCalls[ar->name] = fc;
        fc->numbers = 0;
        fc->time = 0;
    }
    
    shared_ptr<FunctionCallInfo> fc1 = functionCalls[ar->name];
    
    if(!ar->event) {
        fc1->startTime = chrono::steady_clock::now();
    }else {
        auto diff = chrono::steady_clock::now()-fc1->startTime;
        auto d = chrono::duration<double, milli> (diff).count();
        fc1->time += d;
        fc1->numbers++;
    }
}

void LuaP::WriteLog() {
    DDLogInfo(@"BEGIN:");
    for(auto f : functionCalls){
        auto fn = f.first;
        auto fc = f.second;
        //LOG(INFO) << "FC:" << fn << ":" << fc->time << ":" << fc->numbers;
        DDLogInfo(@"FC:%s:%lf:%d", fn.c_str(), fc->time, fc->numbers);
    }
    
    functionCalls.clear();
    
    size_t totalMem = 0;
    for(auto m : totalMemory) {
        auto t = m.first;
        auto sz = m.second->size;
        //LOG(INFO) << "MEM:" << t << ":" << sz;
        DDLogInfo(@"MEM:%lu:%lu", t, sz);
        totalMem += sz;
    }
    
    for(auto m : freeSize) {
        auto t = m.first;
        auto sz = m.second->size;
        //LOG(INFO) << "FREE:" << t << ":" << sz;
        DDLogInfo(@"FREE:%lu:%lu", t, sz);
        m.second->size = 0;
    }
    
    
    //LOG(INFO) << "TOTALMEM:" << totalMem;
    DDLogInfo(@"TOTALMEM:%lu", totalMem);
    //totalMemory.clear();
    //memoryBlocks.clear();
    DDLogInfo(@"END:");
}

void LuaP::Stop() {
    //el::Loggers::flushAll();
    [DDLog flushLog];
}

extern "C" {
    
    static int profiler_init(lua_State *L){
        LuaP::getInstance().Start(L);
        return 0;
    }
    static int profiler_stop(lua_State *L) {
        LuaP::getInstance().Stop();
        return 0;
    }
    
    static int profiler_setLogPath(lua_State *L) {
        const char*path = lua_tostring(L, 1);
        LuaP::getInstance().SetLogPath(path);
        return 0;
    }
    static int profiler_writeLog(lua_State *L) {
        LuaP::getInstance().WriteLog();
        return 0;
    }
    
    static const luaL_Reg prof_funcs[] = {
        {"start", profiler_init},
        {"stop", profiler_stop},
        {"setLogPath", profiler_setLogPath},
        {"writeLog", profiler_writeLog},
        {NULL, NULL},
    };
    int luaopen_luaProfiler(lua_State *L) {
        luaL_openlib(L, "luaProfiler", prof_funcs, 0);
        return 1;
    }
}
